Skip to content

feat: Email Accounts per Store#680

Draft
Copilot wants to merge 6 commits intodevelopfrom
copilot/add-email-functionality-per-store
Draft

feat: Email Accounts per Store#680
Copilot wants to merge 6 commits intodevelopfrom
copilot/add-email-functionality-per-store

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 26, 2026

  • Add StoreId property to EmailAccount domain entity
  • Add GetEmailAccountsByStore(storeId) to IEmailAccountService and implementation
  • Update MessageProviderService.GetEmailAccountOfMessageTemplate to prefer store-specific account as fallback
  • Add StoreId to EmailAccountModel in AdminShared
  • Update EmailAccountProfile mapper to include StoreId
  • Update EmailAccountViewModelService to be async and populate available stores
  • Update Admin EmailAccountController for async PrepareEmailAccountModel
  • Add EmailAccountController to Grand.Web.Store with CRUD actions (store-isolated)
  • Add views in Grand.Web.Store/Areas/Store/Views/EmailAccount/
  • Add new resource strings to DefaultLanguage.xml
  • Fix dirty data: GetEmailAccountsByStore filters from GetAllEmailAccounts cache
  • Remove PrepareAvailableStores/PrepareEmailAccountModel calls from Store controller — Store area never shows the store selector and must not load all stores

Copilot AI and others added 2 commits April 26, 2026 18:47
- Add StoreId property to EmailAccount domain entity
- Add GetEmailAccountsByStore(storeId) method to IEmailAccountService and EmailAccountService
- Add EMAILACCOUNT_BY_STORE_KEY cache key
- Update MessageProviderService to use store-specific email account as fallback
- Update EmailAccountModel with StoreId and AvailableStores
- Update EmailAccountProfile mapper to include StoreId
- Update EmailAccountViewModelService to be async and populate available stores
- Add EmailAccountController to Grand.Web.Store with CRUD + store isolation
- Add EmailAccount views (List, Create, Edit, Partials/CreateOrUpdate) in Store area
- Add Messages namespace to Store _ViewImports.cshtml
- Update Admin EmailAccountController for async PrepareEmailAccountModel

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/094d04cc-7d75-4a00-af14-f5bb77f05736

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/094d04cc-7d75-4a00-af14-f5bb77f05736

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Comment thread src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs Fixed
Comment thread src/Web/Grand.Web.Store/Controllers/EmailAccountController.cs Fixed
KrzysztofPajak and others added 2 commits April 27, 2026 05:51
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
if (string.IsNullOrEmpty(storeId))
return await GetAllEmailAccounts();

var key = string.Format(CacheKey.EMAILACCOUNT_BY_STORE_KEY, storeId);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot when we update email account the data is dirty

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d7be617. The separate EMAILACCOUNT_BY_STORE_KEY cache was the root cause — it held its own object instances that could become stale/mutated independently when an account's StoreId changed during an update. GetEmailAccountsByStore now filters in-memory from the already correctly-invalidated GetAllEmailAccounts cache, so there's only a single source of truth. The unused EMAILACCOUNT_BY_STORE_KEY constant has also been removed.


protected virtual async Task<EmailAccount> GetEmailAccountOfMessageTemplate(MessageTemplate messageTemplate,
string languageId)
string languageId, string storeId = "")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Does MessageTemplate contain storeId?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MessageTemplate does not have a single StoreId — it uses LimitedToStores (bool) + Stores (list of store IDs), which is the standard GrandNode store-scoping pattern for multi-store visibility.

The storeId in GetEmailAccountOfMessageTemplate comes from the call site (e.g. the order-placed notification passes the order's StoreId). It's already threaded correctly through GetMessageTemplate(messageTemplateName, storeId) for the template lookup, and is now reused for the store-specific email account fallback.

…vent dirty data

Instead of caching per-store results separately (which could hold stale/mutated
objects when an account's StoreId changes), filter directly from the already
correctly-invalidated GetAllEmailAccounts cache. Also removes the now-unused
EMAILACCOUNT_BY_STORE_KEY constant.

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/5697bb0d-b979-44be-8f1a-2f8c98ad8b5e

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
@KrzysztofPajak
Copy link
Copy Markdown
Member

@copilot Grand.Web.Store should not see all available store

…lableStores/PrepareEmailAccountModel calls

Agent-Logs-Url: https://github.com/grandnode/grandnode2/sessions/d1fe358f-d64b-47c9-af25-cea72fae9de0

Co-authored-by: KrzysztofPajak <16772986+KrzysztofPajak@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 27, 2026

@copilot Grand.Web.Store should not see all available store

Fixed in 84fdff0. The Store controller was calling PrepareEmailAccountModel() and PrepareAvailableStores() which both invoke GetAllStores() internally. Since the Store view already renders StoreId as a hidden field (not a dropdown) and the value is always locked to CurrentStoreId, all those calls have been removed. The Create GET action now builds the model directly (just sets Port = 25 and StoreId = CurrentStoreId) without touching the store service at all.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • ccsca2021.crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • ccsca2021.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • crl.certum.pl
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)
  • subca.ocsp-certum.com
    • Triggering command: /usr/share/dotnet/dotnet dotnet build src/Web/Grand.Web.Store/Grand.Web.Store.csproj -v q (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants